package tcln.gxtx.client.widget.treegrid.model;

import java.util.ArrayList;
import java.util.List;

import tcln.gxtx.client.utils.ChangeTrackable;
import tcln.gxtx.client.utils.lang.GwtBooleanUtils;
import tcln.gxtx.client.utils.lang.GwtObjectUtils;
import tcln.gxtx.client.utils.lang.GwtStringUtils;

import com.extjs.gxt.ui.client.data.BaseTreeModel;
import com.extjs.gxt.ui.client.data.ModelData;
import com.extjs.gxt.ui.client.data.TreeModel;

/**
 * Abstract tree model.
 * 
 * @author Truong Chau Lien Ngoc (lienngoc@gmail.com)
 */
@SuppressWarnings("serial")
public abstract class AbstractTreeModel extends BaseTreeModel implements ChangeTrackable {

    /**
     * Property 'name'.
     */
    public static final String PROPERTY_NAME = "name";

    /**
     * Property 'id'.
     */
    public static final String PROPERTY_ID = "id";

    private static final String PROPERTY_DEPTH = "depth";
    private static final String PROPERTY_CHANGE_STATE = "state";

    private static final String PROPERTY_FILTERED = "filtered";

    /**
     * Default constructor, which is usually a root model.
     */
    public AbstractTreeModel() {
        set(PROPERTY_DEPTH, 0);
    }

    /**
     * Returns the unique ID of this model in a treegrid.
     * 
     * @return the id.
     */
    public String getId() {
        Object id = get(PROPERTY_ID);
        if (id instanceof String) {
            return (String) id;
        } else {
            return id.toString();
        }
    }

    /**
     * Returns the unique ID of this model as an integer. Notice that if this property is really an integer, this value
     * is returned immediately. Otherwise, the string presentation of this property's value will be converted to
     * {@link Integer}. This means, runtime error may occur in this case.
     * 
     * @return
     */
    public Integer getIdAsInteger() {
        Object id = get(PROPERTY_ID);
        if (id instanceof Number) {
            return ((Number) id).intValue();
        } else {
            return Integer.valueOf(id.toString());
        }
    }

    /**
     * Sets new unique ID of this model in a treegrid.
     * 
     * @param id
     *            the new id to set
     */
    public void setId(String id) {
        set(PROPERTY_ID, id);
    }

    /**
     * Returns a flag indicating that this model has been filtered and should NOT be rendered.
     * <p>
     * This flag is useful in 2 cases:
     * <ol>
     * <li>On rendering the owner tree.
     * <li>On filtering after rendering the tree. See
     * {@link tcln.gxtx.client.widget.treegrid.AbstractTreeGrid#applyFilters()} for details.
     * </ol>
     * 
     * @return the filtered.
     */
    public boolean isFiltered() {
        return GwtBooleanUtils.toBoolean((Boolean) get(PROPERTY_FILTERED));
    }

    /**
     * Sets new value for a flag indicating that this model has been filtered when this model is built and should NOT be
     * rendered.
     * 
     * @param filtered
     *            the filtered to set.
     */
    public void setFiltered(boolean filtered) {
        set(PROPERTY_FILTERED, filtered);
    }

    /**
     * Returns current display text of this model. This text should be used in the first column of a treegrid - that is,
     * the treegrid column.
     * 
     * @return the diplay name.
     */
    public String getName() {
        return get(PROPERTY_NAME);
    }

    /**
     * Sets new display text of this model.
     * 
     * @param name
     *            the new name to set
     */
    public void setName(String name) {
        set(PROPERTY_NAME, name);
    }

    private void resetDepth() {
        set(PROPERTY_DEPTH, Integer.MIN_VALUE);
    }

    /**
     * Determine this tree table item is in which level in tree.
     * 
     * @return
     */
    public final int getDepth() {
        Integer depth = get(PROPERTY_DEPTH);

        if (depth == null || depth < 0) {
            TreeModel parent = this;
            int level = 0;
            while ((parent = parent.getParent()) != null) {
                level++;
            }
            depth = level;
            set(PROPERTY_DEPTH, depth);
        }

        return depth;
    }

    /**
     * Checks if current tree model is actually the 'root' node in an owner tree.
     * 
     * @return
     */
    public final boolean isRoot() {
        return getParent() == null;
    }

    /**
     * {@inheritDoc}
     */
    public ChangeState getState() {
        return (ChangeState) get(PROPERTY_CHANGE_STATE);
    }

    /**
     * {@inheritDoc}
     */
    public void setState(ChangeState newState) {
        set(PROPERTY_CHANGE_STATE, newState);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setParent(TreeModel parent) {
        if (!GwtObjectUtils.equals(getParent(), parent)) {
            super.setParent(parent);

            // refresh depth
            resetDepth();
            getDepth();
        }
    }

    /**
     * Removes current node from its parent if possible.
     */
    public final void removeFromParent() {
        if (!isRoot()) {
            getParent().remove(this);
            resetDepth();
        }
    }

    /**
     * Returns 'visible' children. A visible child model is a model whose state is not {@link ChangeState#REMOVED}.
     * 
     * @return A new list of visible children.
     */
    public final List<AbstractTreeModel> getVisibleChildren() {
        List<AbstractTreeModel> results = new ArrayList<AbstractTreeModel>(getChildCount());
        for (ModelData m : getChildren()) {
            if (m == null || !(m instanceof AbstractTreeModel)) {
                continue;
            }
            AbstractTreeModel model = (AbstractTreeModel) m;
            if (model.getState() != ChangeState.REMOVED) {
                results.add((AbstractTreeModel) model);
            }
        }
        return results;
    }

    /**
     * Returns the number of 'visible' children. A visible child model is a model whose state is not
     * {@link ChangeState#REMOVED}.
     * 
     * @return The number of visible children.
     */
    public final int getVisibleChildCount() {
        return getVisibleChildren().size();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final boolean isLeaf() {
        return super.isLeaf() || (getVisibleChildren().size() == 0);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return GwtStringUtils.getClassSimpleName(getClass()) + "->" + String.valueOf(map);
    }
}
